#import "IRCTask.h"
#import "poker.h"

@implementation IRCTask

+ (id)sharedIRCTask {
   static IRCTask *task = nil;
   if (task == nil) {
	task = [[IRCTask alloc] init];
    }
    return task;
}

#define IRC_EXECUTABLE [[NSBundle mainBundle] pathForResource:@"irc" ofType:@""]

- (void)pushStringOverWire:(const char *)buf {
    [[to_irc fileHandleForWriting] writeData:[NSData dataWithBytes:buf length:strlen(buf)]];
}

- (void)stringToTBot:(const char *)buf {
    [self pushStringOverWire:[[NSString stringWithFormat:@"/msg tbot p %s\n",buf]cString]];
}

- (void)returnFromVacation {
    [self stringToTBot:"back"];
}

- (void)goOnVacation {
    [self stringToTBot:"vacation"];
}

- (void)bet:(int)amount {
    [self stringToTBot:[[NSString stringWithFormat:@"raise %d",amount]cString]];
}

- (void)minBet {
    [self stringToTBot:"TODO"];
}

- (void)pushAllIn {
    [self stringToTBot:"raise 2000000"];
}

- (void)call {
    [self stringToTBot:"call"];
}

- (void)fold {
    [self stringToTBot:"fold"];
}

- (void)halfPot {
    [self stringToTBot:"TODO"];
}

- (int)pot {
    [self stringToTBot:"raise pot"];
    return 0;
}

- (void)leaveTable{
    [self stringToTBot:"quit"];
}

- (void)joinTable{
    [self pushStringOverWire:"/join #tourney\n"];
    [self stringToTBot:"join i"];
}


- (Card *)cardFromRankChar:(char)rank_char suitChar:(char)suit_char
{
  const char *suits = "Xcshd";
  const char *ranks = "AKQJT98765432";
  Card *retval;
  Suit s;
  Rank r;
  char *sp;
  char *rp;

  s = 0;
  r = 0;
  sp = strchr (suits, suit_char);
  rp = strchr (ranks, rank_char);
  if (sp && rp)
    {
      s = sp - suits;
      r = rp - ranks;
    }
  retval = [Card cardWithSuit:s rank:r];
  return retval;
} 

#define TRY_TO_MATCH(sscanf_call, n_expected, method_call) \
  do                                                       \
    if (sscanf sscanf_call == n_expected)                  \
      {                                                    \
        method_call;                                       \
        goto SUCCESS;                                      \
      }                                                    \
  while (FALSE)

-(void) addPlayerHelper:(const char *) player cash:(int) amount char1:(char) c1 char2:(char)c2
{
  BOOL is_vaced;
  BOOL is_button;
  Player *p = [pokerTable findPlayerWithName:player];
  [pokerTable addPlayer:player cash:amount];
  is_vaced = c1 == 'V';
  is_button = (c1 == 'B') || (c2 == 'B');
  [p setOnVacation:is_vaced];
  if (is_button) [pokerTable setButtonPlayer:player];

}

- (BOOL) parseLine:(NSData *)line
{
  char *buf, *orig_buf;
  char *player;
  int n_players;
  BOOL retval;
  int nleft;
  char bang, dot;
  int small_blind;
  int big_blind;
  int blind_remaining;
  char *blind_remaining_units;
  int hand_no;
  int blind;
  int pot;
  int to_call;
  int amount;
  char *by_whom;
  char r1, r2, r3, r4, r5;
  char s1, s2, s3, s4, s5;
  char colon;
  char *hand_string;
  char *message_from;
  volatile BOOL is_public;

  buf = alloca ([line length] + 1);
  [line getBytes:buf];
  buf[[line length]] = 0;
  orig_buf = buf;

  /* strip portion that shows this coming from TBot */
  if (buf[0] != '<' && buf[0] != '-' && buf[0] != '*')
    return FALSE;
  else
    {
      char desired;

      desired = ( buf[0] == '<') ? '>' : buf[0];
      buf = strchr (buf+1, ' ');
      if (!buf || buf[-1] != desired)
	return FALSE;
      else
	{
	  int len;

          --buf;
	  len = buf - orig_buf - 1;
	  if (len <= 0)
	    return FALSE;
	  message_from = alloca (len + 1);
	  memcpy (message_from, orig_buf+1, len);
	  message_from[len] = 0;
	  buf += 2;
	  is_public = orig_buf[0] == '<';
	}
    }

  player = alloca ([line length] + 1);
  blind_remaining_units = alloca ([line length] + 1);
  by_whom = alloca ([line length] + 1);
  hand_string = alloca ([line length] + 1);
  retval = TRUE;

  if (strcmp (message_from, "TBot") != 0) {
        [pokerTable player:message_from messages:buf];
	return TRUE;
  }

#if 0
  TRY_TO_MATCH
    (
     (buf, "%s has joined the game.  We now have %d players.", player, &nleft), 2,
     [pokerTable addPlayer:player cash:5000]);
#endif

  TRY_TO_MATCH
    (
     (buf, "%s quits.  We now have %d players.", player, &nleft), 2,
     [pokerTable playerQuits:player n_left:nleft]);

  TRY_TO_MATCH
    (
     (buf, "Tourney started by %s!", player), 1,
       [pokerTable readyToBeginGame]);

  TRY_TO_MATCH
    (
     (buf, "The blinds are currently $%d and $%d.", &small_blind, &big_blind), 2,
     [pokerTable blindsAre:small_blind and:big_blind]);

  TRY_TO_MATCH
    (
     (buf, "Blinds will double in %d %s.", &blind_remaining, blind_remaining_units), 2,
     [pokerTable blindsWillDoubleIn:blind_remaining units:blind_remaining_units]);

  TRY_TO_MATCH
    (
     (buf, "Game #%d, %d players.  Dealing Holdem high", &hand_no, &n_players), 2,
     [pokerTable beginAddingPlayers]);

  TRY_TO_MATCH
    (
     (buf, "%s blinds $%d.  Pot is now $%d.", player, &blind, &pot), 3,
     [pokerTable player:player blinds:blind potNow:pot allIn:NO]);

  TRY_TO_MATCH
    (
     (buf, "%s blinds $%d and is all in.  Pot is now $%d.", player, &blind, &pot), 3,
     [pokerTable player:player blinds:blind potNow:pot allIn:YES]);

  TRY_TO_MATCH
    (
     (buf, "%s is next to act. (%d to call)", player, &to_call), 2,
     [pokerTable player:player toCall:to_call]);

  TRY_TO_MATCH
  (  
   (buf, "%s folds.  We now have %d players in the hand.", player, &n_players), 2,
   [pokerTable player:player foldsLeaving:n_players]);

  TRY_TO_MATCH
  (
   (buf, "%s calls $%d.  Pot is now $%d.", player, &amount, &pot), 3,
     [pokerTable player:player calls:amount potNow:pot allIn:NO]);

  TRY_TO_MATCH
    (
     (buf, "%s calls $%d and is all in.  Pot is now $%d.", player, &amount, &pot), 3,
     [pokerTable player:player calls:amount potNow:pot allIn:YES]);

  TRY_TO_MATCH
    (
     (buf, "%s has sent %s on vacation!", player, by_whom), 2,
     [pokerTable player:player vacationedBy:by_whom]);

  TRY_TO_MATCH
    (
     (buf, "%s checks%c", player, &dot), 2,
     [pokerTable playerChecks:player]);

  TRY_TO_MATCH
    (
     (buf, "Board:      %c%c %c%c %c%c %c%c %c%c", &r1, &s1, &r2, &s2, &r3, &s3, &r4, &s4, &r5, &s5), 10,
     {[pokerTable publicCardNumber:0 didShowCard:[self cardFromRankChar:r1 suitChar:s1]];
      [pokerTable publicCardNumber:1 didShowCard:[self cardFromRankChar:r2 suitChar:s2]];
      [pokerTable publicCardNumber:2 didShowCard:[self cardFromRankChar:r3 suitChar:s3]];
      [pokerTable publicCardNumber:3 didShowCard:[self cardFromRankChar:r4 suitChar:s4]];
      [pokerTable publicCardNumber:4 didShowCard:[self cardFromRankChar:r5 suitChar:s5]];});

  TRY_TO_MATCH
    (
     (buf, "Board:      %c%c %c%c %c%c %c%c", &r1, &s1, &r2, &s2, &r3, &s3, &r4, &s4), 8,
     {[pokerTable publicCardNumber:0 didShowCard:[self cardFromRankChar:r1 suitChar:s1]];
      [pokerTable publicCardNumber:1 didShowCard:[self cardFromRankChar:r2 suitChar:s2]];
      [pokerTable publicCardNumber:2 didShowCard:[self cardFromRankChar:r3 suitChar:s3]];
      [pokerTable publicCardNumber:3 didShowCard:[self cardFromRankChar:r4 suitChar:s4]];});

  TRY_TO_MATCH
    (
     (buf, "Board:      %c%c %c%c %c%c", &r1, &s1, &r2, &s2, &r3, &s3), 6,
     {[pokerTable publicCardNumber:0 didShowCard:[self cardFromRankChar:r1 suitChar:s1]];
      [pokerTable publicCardNumber:1 didShowCard:[self cardFromRankChar:r2 suitChar:s2]];
      [pokerTable publicCardNumber:2 didShowCard:[self cardFromRankChar:r3 suitChar:s3]];});

  TRY_TO_MATCH
    (
     (buf, "%s bets $%d.  Pot is now $%d.", player, &amount, &pot), 3,
     [pokerTable player:player bets:amount potNow:pot allIn:NO]);

  TRY_TO_MATCH
    (
     (buf, "%s bets $%d and is all in.  Pot is now $%d.", player, &amount, &pot), 3,
     [pokerTable player:player bets:amount potNow:pot allIn:YES]);

  TRY_TO_MATCH
    (
     (buf, "%s is back from vacation%c", player, &bang), 2,
     [pokerTable playerBackFromVacation:player]);

  TRY_TO_MATCH
    (
     (buf, "%s wins $%d.", player, &amount), 2,
     [pokerTable player:player wins:amount withHand:NULL]);

  TRY_TO_MATCH
    (
     (buf, "High: %s wins $%d with %s.", player, &amount, hand_string), 3,
     [pokerTable player:player wins:amount withHand:hand_string]);

  TRY_TO_MATCH
    (
     (buf, "Players' hands%c", &colon), 1,
     [pokerTable aboutToShowHands]);

  TRY_TO_MATCH
    (
     (buf, "Your hole cards are: %c%c %c%c", &r1, &s1, &r2, &s2), 4,
     [pokerTable userGotFirstHoleCard:[self cardFromRankChar:r1 suitChar:s1]
                  secondCard:[self cardFromRankChar:r2 suitChar:s2]]);

  TRY_TO_MATCH
    (
     (buf, "%s : %c%c %c%c", player, &r1, &s1, &r2, &s2), 5,
     {Player *p;
      p = [pokerTable findPlayerWithName:player];
      [[p holeHand] setFirstHoleCard:[self cardFromRankChar:r1 suitChar:s1]];
      [[p holeHand] setSecondHoleCard:[self cardFromRankChar:r2 suitChar:s2]];});

  TRY_TO_MATCH
    (
     (buf, "%s raises $%d.  Pot is now $%d.", player, &amount, &pot), 3,
     [pokerTable player:player raises:amount potNow:pot allIn:NO]);

  TRY_TO_MATCH
    (
     (buf, "%s raises $%d and is all in.  Pot is now $%d.", player, &amount, &pot), 3,
     [pokerTable player:player raises:amount potNow:pot allIn:YES]);

  TRY_TO_MATCH
    (
     (buf, "%s is busted%c", player, &bang), 2,
     [pokerTable playerBusted:player]);

  TRY_TO_MATCH
    (
     (buf, "%s has quit%c", player, &dot), 2,
     [pokerTable playerQuit:player]);

  if (buf[0] == ' ' || buf[0] == 'V' || buf[0] == 'B')
    {
      char c1, c2;

      if (sscanf (buf, "%c%c%s %d", &c1, &c2, player, &amount) == 4)
	{
          [self addPlayerHelper:player cash:amount char1:c1 char2:c2];
          buf = strchr (buf+2, ' ');
	  while (*buf == ' ')
	    ++buf;
	  buf = strchr (buf, ' ');
#if 0
	  while (strncmp (buf, "   ", 3) == 0 || (*buf == ' ' && buf[1] != ' '))
	    ++buf;
#else
	  buf += 3;
#endif
          if (sscanf (buf, "%c%c%s %d", &c1, &c2, player, &amount) == 4)
            {
              [self addPlayerHelper:player cash:amount char1:c1 char2:c2];
              buf = strchr (buf+2, ' ');
              while (*buf == ' ')
                ++buf;
              buf = strchr (buf, ' ');
#if 0
              while (strncmp (buf, "   ", 3) == 0 || (*buf == ' ' && buf[1] != ' '))
                ++buf;
#else
	      buf += 3;
#endif
              if (sscanf (buf, "%c%c%s %d", &c1, &c2, player, &amount) == 4)
                  [self addPlayerHelper:player cash:amount char1:c1 char2:c2];
	    }
        }
    }

  retval = FALSE;
 SUCCESS:
  return retval;
}


- (void) gotSomeData:(NSNotification *)note
{
  NSData *data;
  const char *start_bytes, *stop_bytes;
  int length;

#warning THIS CODE ASSUMES LINE WONT SPLIT ACROSS DATA

  data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem];
  start_bytes = [data bytes];
  length = [data length];
  stop_bytes = start_bytes;
  while (length > 0)
    {
      while (length > 0 && *stop_bytes != '\n')
	{
	  ++stop_bytes;
	  --length;
	}
      if (stop_bytes > start_bytes)
	{
	  NSData *new_data;

	  new_data = [NSData dataWithBytes:start_bytes length:stop_bytes - start_bytes];
	  [pokerTable setStatusString:[[[NSString alloc]initWithData:new_data encoding:[NSString defaultCStringEncoding]]autorelease]];
	  [self parseLine:new_data];
	}
      start_bytes = stop_bytes + 1;
      stop_bytes = start_bytes;
      --length;
    }
  [[from_irc fileHandleForReading] readInBackgroundAndNotify];
}

- (void)makeIRCConnection:(PokerTable *)table
{
    NSMutableArray *args = [NSMutableArray array];
    pokerTable = table;
    aTask = [[NSTask alloc] init];
    to_irc = [NSPipe pipe];
    from_irc = [NSPipe pipe];
#if 1
    [args addObject:@"-d"];
    [args addObject:@"deadhead"];
    [args addObject:@"irc.poker.net"];
    [aTask setLaunchPath:IRC_EXECUTABLE];
#else
    [args addObject:@"/Local/Users/machack/ctm/poker/tbot2/typescript_FIRST_OFFICIAL_SCORE"];
    [aTask setLaunchPath:@"/bin/cat"];
#endif
    [aTask setArguments:args];
    [aTask setStandardInput:to_irc];
    [aTask setStandardOutput:from_irc];
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(gotSomeData:)
     name:NSFileHandleReadCompletionNotification object:nil];

    [[from_irc fileHandleForReading] readInBackgroundAndNotify];
    [pokerTable newPokerTable];
    [aTask launch];

}
@end
